package com.twinkle.note.biz.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.RandomUtil;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.base.Preconditions;
import com.twinkle.framework.biz.context.holder.LoginUserContextHolder;
import com.twinkle.framework.common.exception.BusinessException;
import com.twinkle.framework.common.response.Response;
import com.twinkle.framework.common.utils.JsonUtils;
import com.twinkle.note.biz.constant.RedisKeyConstants;
import com.twinkle.note.biz.enums.NoteStatusEnum;
import com.twinkle.note.biz.enums.NoteTypeEnum;
import com.twinkle.note.biz.enums.NoteVisibleEnum;
import com.twinkle.note.biz.enums.ResponseCodeEnum;
import com.twinkle.note.biz.mapper.NoteMapper;
import com.twinkle.note.biz.mapper.TopicMapper;
import com.twinkle.note.biz.model.entity.Note;
import com.twinkle.note.biz.model.vo.FindNoteDetailReqVO;
import com.twinkle.note.biz.model.vo.FindNoteDetailRspVO;
import com.twinkle.note.biz.model.vo.PublishNoteReqVO;
import com.twinkle.note.biz.model.vo.UpdateNoteReqVO;
import com.twinkle.note.biz.rpc.DistributedIdGeneratorRpcService;
import com.twinkle.note.biz.rpc.KeyValueRpcService;
import com.twinkle.note.biz.rpc.UserRpcService;
import com.twinkle.note.biz.service.NoteService;
import com.twinkle.user.dto.resp.FindUserByIdRspDTO;
import jakarta.annotation.Resource;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class NoteServiceImpl implements NoteService {
    @Resource
    private DistributedIdGeneratorRpcService distributedIdGeneratorRpcService;
    @Resource
    private KeyValueRpcService keyValueRpcService;
    @Resource
    private TopicMapper  topicMapper;
    @Resource
    private NoteMapper noteMapper;
    @Resource
    private UserRpcService userRpcService;
    @Resource(name = "taskExecutor")
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;
    @Resource
    private RedisTemplate<String, String> redisTemplate;

    private static final Cache<Long, String> LOCAL_CACHE = Caffeine.newBuilder()
            .initialCapacity(10000) // 设置初始容量为 10000 个条目
            .maximumSize(10000) // 设置缓存的最大容量为 10000 个条目
            .expireAfterWrite(1, TimeUnit.HOURS) // 设置缓存条目在写入后 1 小时过期
            .build();

    @Override
    public Response<?> publishNote(PublishNoteReqVO publishNoteReqVO) {
        Integer type = publishNoteReqVO.getType();
        NoteTypeEnum noteTypeEnum = NoteTypeEnum.valueOf(type);

        if (Objects.isNull(noteTypeEnum)) {
            throw new BusinessException(ResponseCodeEnum.NOTE_TYPE_ERROR);
        }
        String imgUris = null;
        String videoUri = null;
        Boolean isContentNull = true;

        switch (noteTypeEnum) {
            case IMAGE_TEXT:
                List<String> imgUriList = publishNoteReqVO.getImgUris();
                Preconditions.checkArgument(CollUtil.isNotEmpty(imgUriList), "笔记图片不能为空！");
                Preconditions.checkArgument(imgUriList.size() <= 8, "笔记图片不能多于8张");
                imgUris = StringUtils.join(imgUriList, ",");
                break;
            case VIDEO:
                videoUri = publishNoteReqVO.getVideoUri();
                Preconditions.checkArgument(StringUtils.isNotBlank(videoUri), "笔记视频不能为空");
                break;
            default:
                break;
        }
        String snowflakeIdId = distributedIdGeneratorRpcService.getSnowflakeId();
        String contentUuid = null;

        String content = publishNoteReqVO.getContent();
        if (Objects.isNull(content)) {
            isContentNull = false;
            contentUuid = UUID.randomUUID().toString();
            boolean isSavedSuccess = keyValueRpcService.saveNoteContent(contentUuid, content);
            if (!isSavedSuccess) {
                throw new BusinessException(ResponseCodeEnum.NOTE_PUBLISH_FAIL);
            }
        }
        Long topicId = publishNoteReqVO.getTopicId();
        String topicName = null;
        if (Objects.nonNull(topicId)) {
            topicName = topicMapper.selectNameByPrimaryKey(topicId);
        }
        Long creatorId = LoginUserContextHolder.getUserId();

        Note note = Note.builder()
                .id(Long.valueOf(snowflakeIdId))
                .isContentEmpty(isContentNull)
                .creatorId(creatorId)
                .imgUris(imgUris)
                .title(publishNoteReqVO.getTitle())
                .topicId(topicId)
                .topicName(topicName)
                .type(type)
                .visible(NoteVisibleEnum.PUBLIC.getCode())
                .createTime(LocalDateTime.now())
                .updateTime(LocalDateTime.now())
                .status(NoteStatusEnum.NORMAL.getCode())
                .isTop(Boolean.FALSE)
                .videoUri(videoUri)
                .contentUuid(contentUuid)
                .build();
        try{
            noteMapper.insert(note);
        }catch (Exception e){
            log.error("==> 笔记存储失败", e);
            if (StringUtils.isNotBlank(contentUuid)) {
                keyValueRpcService.deleteNoteContent(contentUuid);
            }
        }
        return Response.success();
    }

    @SneakyThrows
    @Override
    public Response<FindNoteDetailRspVO> findNoteDetail(FindNoteDetailReqVO findNoteDetailReqVO) {
        Long noteId = findNoteDetailReqVO.getId();
        long userId = LoginUserContextHolder.getUserId();

        // 本地缓存查询笔记是否存在
        String noteDetailLocalCache = LOCAL_CACHE.getIfPresent(noteId);
        if (StringUtils.isNotBlank(noteDetailLocalCache)) {
            FindNoteDetailRspVO findNoteDetailRspVO = JsonUtils.parseObject(noteDetailLocalCache, FindNoteDetailRspVO.class);
            checkNoteVisibleFromVO(userId, findNoteDetailRspVO);
            return Response.success(findNoteDetailRspVO);
        }

        // redis查询笔记是否存在
        String noteDetailRedisKey = RedisKeyConstants.buildNoteDetailKey(noteId);
        String noteDetailJson = redisTemplate.opsForValue().get(noteDetailRedisKey);
        if (StringUtils.isNotBlank(noteDetailJson)) {
            FindNoteDetailRspVO findNoteDetailRspVO = JsonUtils.parseObject(noteDetailJson, FindNoteDetailRspVO.class);
            threadPoolTaskExecutor.submit(() -> {
               LOCAL_CACHE.put(noteId, noteDetailLocalCache);
            });
            // 可见性校验
            if (Objects.nonNull(findNoteDetailRspVO)) {
                Integer visible = findNoteDetailRspVO.getVisible();
                checkNoteVisible(visible, userId, findNoteDetailRspVO.getCreatorId());
            }
            return Response.success(findNoteDetailRspVO);
        }

        // 数据库查询笔记是否存在
        Note note = noteMapper.selectByPrimaryKey(noteId);

        if (Objects.isNull(note)) {
            threadPoolTaskExecutor.execute(() -> {
                long expireSeconds = 60 + RandomUtil.randomInt(60);
                redisTemplate.opsForValue().set(noteDetailRedisKey, "null", expireSeconds, TimeUnit.SECONDS);
            });
            throw new BusinessException(ResponseCodeEnum.NOTE_NOT_FOUND);
        }
        // 判断可见性
        Integer visible = note.getVisible();
        checkNoteVisible(visible, userId, note.getCreatorId());
        // 获取发布者信息
        Long creatorId = note.getCreatorId();
        CompletableFuture<FindUserByIdRspDTO> userResultFuture = CompletableFuture.supplyAsync(
                () -> userRpcService.findById(creatorId), threadPoolTaskExecutor
        );
        //获取笔记内容
        CompletableFuture<String> contentResultFuture = CompletableFuture.completedFuture(null);
        if (Objects.equals(note.getIsContentEmpty(), Boolean.FALSE)){
            contentResultFuture = CompletableFuture.supplyAsync(
                    () -> keyValueRpcService.findNoteContent(note.getContentUuid()), threadPoolTaskExecutor
            );
        }
        CompletableFuture<String> finalContentResultFuture = contentResultFuture;
        CompletableFuture<FindNoteDetailRspVO> resultFuture = CompletableFuture.allOf(userResultFuture,contentResultFuture)
                .thenApply(s -> {
                   FindUserByIdRspDTO findUserByIdRspDTO = userResultFuture.join();
                   String content = finalContentResultFuture.join();
                    Integer type = note.getType();
                    String imgUrisStr = note.getImgUris();
                    List<String> imgUris = null;
                    if (type.equals(NoteTypeEnum.IMAGE_TEXT.getCode()) && StringUtils.isNotBlank(imgUrisStr)) {
                        imgUris = List.of(imgUrisStr.split(","));
                    }
                    // 构建返参 VO 实体类
                    return FindNoteDetailRspVO.builder()
                            .id(note.getId())
                            .type(note.getType())
                            .title(note.getTitle())
                            .content(content)
                            .imgUris(imgUris)
                            .topicId(note.getTopicId())
                            .topicName(note.getTopicName())
                            .creatorId(note.getCreatorId())
                            .creatorName(findUserByIdRspDTO.getNickName())
                            .avatar(findUserByIdRspDTO.getAvatar())
                            .videoUri(note.getVideoUri())
                            .updateTime(note.getUpdateTime())
                            .visible(note.getVisible())
                            .build();

                });
        FindNoteDetailRspVO findNoteDetailRspVO = resultFuture.get();
        threadPoolTaskExecutor.submit(() ->{
            String findNoteDetailRspVOStr = JsonUtils.toJsonString(findNoteDetailRspVO);
            long expireSeconds = 60*60*24 + RandomUtil.randomInt(60*60*24);
            redisTemplate.opsForValue().set(noteDetailRedisKey, findNoteDetailRspVOStr, expireSeconds, TimeUnit.SECONDS);
        });
        return Response.success(findNoteDetailRspVO);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Response<?> updateNote(UpdateNoteReqVO updateNoteReqVO) {
        Long noteId = updateNoteReqVO.getId();
        Integer type = updateNoteReqVO.getType();

        NoteTypeEnum noteTypeEnum = NoteTypeEnum.valueOf(type);

        if (Objects.isNull(noteTypeEnum)) {
            throw new BusinessException(ResponseCodeEnum.NOTE_TYPE_ERROR);
        }
        String imgUris = null;
        String videoUri = null;
        switch (noteTypeEnum) {
            case IMAGE_TEXT:
                List<String> imgUrisList = updateNoteReqVO.getImgUris();
                Preconditions.checkArgument(CollUtil.isNotEmpty(imgUrisList),"笔记图片不能为空");
                Preconditions.checkArgument(imgUrisList.size() <= 8,"笔记图片不能多于 8 张");
                imgUris = StringUtils.join(imgUrisList, ",");
                break;
            case VIDEO:
                videoUri = updateNoteReqVO.getVideoUri();
                Preconditions.checkArgument(StringUtils.isNotBlank(videoUri),"笔记视频不能为空");
                break;
            default:
                break;
        }
        Long topicId = updateNoteReqVO.getTopicId();
        String topicName = null;
        if (Objects.nonNull(topicId)) {
            topicName = topicMapper.selectNameByPrimaryKey(topicId);
            if (StringUtils.isBlank(topicName)) throw new BusinessException(ResponseCodeEnum.TOPIC_NOT_FOUND);
        }
        String content = updateNoteReqVO.getContent();
        Note note = Note.builder()
                .id(noteId)
                .isContentEmpty(StringUtils.isBlank(content))
                .imgUris(imgUris)
                .title(updateNoteReqVO.getTitle())
                .topicId(updateNoteReqVO.getTopicId())
                .topicName(topicName)
                .type(type)
                .updateTime(LocalDateTime.now())
                .videoUri(videoUri)
                .build();

        noteMapper.updateByPrimaryKey(note);
        // 删除 Redis 缓存
        String noteDetailRedisKey = RedisKeyConstants.buildNoteDetailKey(noteId);
        redisTemplate.delete(noteDetailRedisKey);
        // 删除本地缓存
        LOCAL_CACHE.invalidate(noteId);


        Note note1 = noteMapper.selectByPrimaryKey(noteId);
        String contentUuid = note1.getContentUuid();
        boolean isUpdateContentSuccess = false;
        if (StringUtils.isBlank(content)) {
            isUpdateContentSuccess = keyValueRpcService.deleteNoteContent(contentUuid);
        } else {
            contentUuid = StringUtils.isBlank(contentUuid) ? UUID.randomUUID().toString() : contentUuid;
            isUpdateContentSuccess = keyValueRpcService.saveNoteContent(contentUuid, content);
        }
        if (isUpdateContentSuccess) {
            throw new BusinessException(ResponseCodeEnum.NOTE_UPDATE_FAIL);
        }

        return Response.success();
    }

    private void checkNoteVisible(Integer visible, long userId, Long creatorId) {
        if (Objects.equals(visible, NoteVisibleEnum.PRIVATE.getCode())
                && !Objects.equals(userId, creatorId)) { // 仅自己可见, 并且访问用户为笔记创建者才能访问，非本人则抛出异常
            throw new BusinessException(ResponseCodeEnum.NOTE_PRIVATE);
        }
    }
    private void checkNoteVisibleFromVO(Long userId, FindNoteDetailRspVO findNoteDetailRspVO) {
        if (Objects.nonNull(findNoteDetailRspVO)) {
            Integer visible = findNoteDetailRspVO.getVisible();
            checkNoteVisible(visible, userId, findNoteDetailRspVO.getCreatorId());
        }
    }
}
